<!doctype html>
<html>
<head>
    <!--进行一些网页的显示设置-->
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
    <title>高德地图公交线路数据获取</title>
    <!--连接到高德服务器的一个显示方案，好像是-->
    <link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css"/>
    <style type="text/css">
        /* 网页全局格式设置 */
        h2 {
            color: lightslategray;
            font-size: large;
        }
        p {
            font-size: small;
        }
        .Guide{
            text-align: right;
            position: relative;
            top:-40px;
            color: #90a4ae;
            display: inline-block;
        }
        #Remind{
            width:100%;
            height: 100px;
        }
        .Guide {
            position: relative;
            /*top: -150px;
            right: -100px;*/
            display: inline-block;
            margin: 4em;
            color: darkorange;
        }
        .Guidance {
            position: relative;
            display: none;
            white-space: nowrap;
            transform: translate(-50%, 0);
            background-color: white;
            font-size: small;
            text-align: left;
        }
        .Guide:hover .Guidance {
            display: block;
        }
        #Remind{
            margin: 10%;
            height: 150px;
            width: 90%;
        }

        /* 网页布局 */
        #header{
            position: relative;
            right: 10px;
            text-align: right;
            float: right;
            width: 24%;
            height: 200px;
        }
        #content{
            float: left;
            width: 76%;
            height: 200px;
        }
        #container{
            position: absolute;
            top:200px;
            left: 0;
            bottom: 0;
            z-index: -1;
            width:100%;
        }
        * {
            box-sizing: border-box;
        }
        /*.clearfix::after {
            content: "";
            clear: both;
            display: table;
        }*/
        .box2 {
            float: left;
            width: 19%;
            margin: 1%;
        }
    </style>
    <!-- 公交线路规划的样式-->
    <style type="text/css">
        #panel {
            position: fixed;
            background-color: white;
            max-height: 90%;
            overflow-y: auto;
            top: 10px;
            right: 10px;
            width: 280px;
        }
        #panel .amap-call {
            background-color: #009cf9;
            border-top-left-radius: 4px;
   	        border-top-right-radius: 4px;
        }
        #panel .amap-lib-transfer {
	        border-bottom-left-radius: 4px;
   	        border-bottom-right-radius: 4px;
            overflow: hidden;
        }
    </style>
    <!--高德数据、公交线路搜索接口-->
    <script type="text/javascript" src="https://cache.amap.com/lbs/static/addToolbar.js"></script>
    <script src="https://a.amap.com/jsapi_demos/static/demo-center/js/demoutils.js"></script>
    <!--公交线路查询与公交线路规划插件-->
    <script type="text/javascript">
            window._AMapSecurityConfig = {
                securityJsCode:'11a016edcbe7ad384514ddb829b5af6f',
            }
    </script>
    <script type="text/javascript"
    src="https://webapi.amap.com/maps?v=2.0&key=5060fa2dcb57033708e92fcdff6e5eeb&plugin=AMap.LineSearch,AMap.Transfer">
    </script>
</head>
<body>
<div id="header">
    <br>
    <h1 style="color: #0f89f5;font-size: x-large">高德地图公交线路数据获取脚本</h1>
    <br>
    <p>数据获取不易，科研道路许多愁</p>
    <p style="color: #90a4ae;font-size: x-small">版本号：20210320，有限技术支持：sidchen0@qq.com<br>欢迎关注公众号：规划人在路上</p>
    <div class="Guide">使用说明
        <div class="Guidance">
            1. 确定要获取的公交线路名称文件<br>
            * （需为高德地图认可的线路名称，建议事先爬取公交站点POI数据后使用提供的脚本析出）<br>
            2. 按照页面提示获取公交线路数据<br>
            3. 使用提供的脚本处理第二步获得的json数据<br>
            4. 导入ArcGIS处理生成最终的线路数据<br>
            * （提供ArcGIS 10.6版本的模型，主要步骤：excel转表、表转点（XY事件）、点集转线）<br>
        </div>
    </div>
</div>
<!--读取本地文件控件-->
<div id="content">
    <div class="box2">
        <h2>第一步：选择线路文件</h2>
        <br>
        <input type="file" id="files"/>
        <input type="button" value="读取结果" onclick="result()"/>
    </div>
    <!--开始请求数据控件-->
    <div class="box2">
        <h2>第二步：开始获取数据</h2>
        <p>1.右侧提示“文件读取成功后”，请点击“数据获取”按钮开始获取数据。</br>
          2.使用“进度查询”确认全部获取结束后，如需进一步确认公交线路往返路线是否相同，请点击“往返比对”</p>
        <br>
        <input id="start" type="button" value="获取数据" />
        <input type="button" value="进度查询" onclick="how()"/>
        <input id="LineCheck" type="button" value="往返比对"/>
    </div>
    <div class="box2">
        <!--<input type="text" id="Remind" value=""/>-->
        <textarea id="Remind" style="font-size: small;margin: 5%">处理进程</textarea>
        <script>
            //将内容输出到网页中的文本框中
            function textlog(content){
                let pre = document.getElementById("Remind").value
                document.getElementById("Remind").value=content+'\n'+pre;
            }
        </script>
    </div>
    <!--导出数据控件-->
    <div class="box2">
        <h2>第三步：导出获取数据</h2>
        <p>请使用第二步的“进度查询”按钮确认数据已全部获取，再点击按钮将所需数据下载到本地。下一步：将下载的5个文件移动到“3-LineDataToGIS”脚本所在的“Linedata”文件夹内</p>
        <script src="https://cdn.bootcss.com/FileSaver.js/1.3.8/FileSaver.js"></script>
        <br>
        <input type="button" id="lines" value="单向总表"/>
        <input type="button" id="stops" value="单向站点"/>
        <br>
        <input type="button" id="dots" value="单向线路"/>
        <input type="button" id="lost" value="丢失线路"/>
        <input type="button" id="compare" value="比对数据"/>
    </div>
</div>
<!--读取本地线路数据的脚本-->
<script>
    var inputElement = document.getElementById("files");
    inputElement.addEventListener("change", handleFiles, false);
    var names=[];//读取公交线路名称列表，并储存为数组
    var totalnum;
    function handleFiles() {
        var selectedFile = document.getElementById("files").files[0];//获取读取的File对象
        var name = selectedFile.name;//读取选中文件的文件名
        var size = selectedFile.size;//读取选中文件的大小
        console.log("文件名:"+name+"大小："+size+'kb');
        textlog("文件名:"+name+"大小："+size+'kb')
        var reader = new FileReader();//这里是核心！！！读取操作就是由它完成的。
        reader.readAsText(selectedFile,'utf-8');//读取文件的内容
        reader.onload = function(){
            console.log("文件读取成功");//当读取完成之后会回调这个函数，然后此时文件的内容存储到了result中。直接操作即可。
            textlog("文件读取成功")
            getnames(this.result);
        }
    }
    function getnames(result){
        var rows=result.split('\n');
        for(var i=0;i<rows.length;++i) {
            if (rows[i].length > 1) {
                names.push(rows[i]);//这里面读取文件之后，并不是字符串格式的，
            }
        totalnum=names.length-1;
        }
    }
    //返回线路文件读取结果
    function result(){
        console.log('读取到的公交（地铁）线路共计：',totalnum);//减去首行城市名、尾行空行
        textlog('读取到的公交（地铁）线路共计：'+totalnum)
    }
</script>
<!--实例化高德地图控件-->
<div id="container"></div>
<!--公交线路搜索控件
<div class="input-card" style='width:18rem;'>
    <label style='color:grey'>公交线路查询</label>
<div class="input-item">
        <div class="input-item-prepend"><span class="input-item-text" >线路名称</span></div>
        <input id='BusLineName' type="text" value='919' >
</div>
    <input id="search" type="button" class="btn" value="查询" />
</div>
-->
<!--公交线路规划结果面板-->
<div id="panel"></div>
<!--获取数据-->
<script language="javascript">
    /*创建全局变量，分三个表储存每条线路的信息{name:线路名称,stime:首班时间,etime:末班时间,basic_price:起步票价,total_price:全程票价,
    distance:全程距离},{linename:线路名称,via_stops:途径站点}{linename:线路名称,path:[线路经纬度]}*/
    var buslines=[];
    var busstops=[];
    var linedraw=[];//用于储存path的中间数组
    var drawlines=[];
    var buslost=[];//用于储存未获取的线路
    var BusInfo = [] //储存两个方向的所有线路['LineName', 'FirstDirection', 'SecondDirection']
    var CheckNum;
    /*
     * 主要流程分为三个步骤，即定义了三个函数
     * 1. 首先调用公交路线查询服务(lineSearch)
     * 2. 根据返回结果解析，输出解析结果(lineSearch_Callback)
     * 3. 在地图上绘制公交线路(drawbusLine)
    */
    //实例化高德地图，即使结果显示在网页中
    var map = new AMap.Map("container", {
        resizeEnable: true,
        center: [108.911945, 34.371075],//地图中心点，初始化需要，随意设置
        zoom: 15 //地图显示的缩放级别
    });

    var linesearch;
    //公交线路查询
    function lineSearch(busLineName) {
        //实例化公交线路查询类
        linesearch = new AMap.LineSearch({
            pageIndex: 1,
            city:(names[0]+'2').substr(0,3),//目标城市，所有的处理都是将该数据转为字符串
            pageSize: 1,
            extensions: 'all'//获取详细信息all
        });
        //搜索“buslinename”相关公交线路
        linesearch.search(busLineName, function (status, result) {
            map.clearMap()
            //console.log(result)
            if (status === 'complete' && result.info === 'OK') {
                lineSearch_Callback(result);//成功查询，调用结果解析函数，如下面指定
            } else {
                //该公交线路名称无法查询到线路数据，存入未获取数据的数组——无法
                console.log('未找到',busLineName);
                textlog('!!! 未找到'+busLineName);
                buslost.push(busLineName);
                //alert(result);//alert('网页弹出的错误信息')
            }
        });
    }

    /*公交路线查询服务返回数据解析概况*/
    function lineSearch_Callback(data) {
        var lineArr = data.lineInfo;//公交线路信息的数组，[id、name、path等]
        var lineNum = data.lineInfo.length;//返回该数组的长度，即线路数目（可能公交查询结果有多条？）
        if (lineNum == 0) {
        } else {
            for (var i = 0; i < lineNum; i++) {
                //所有数据转存到全局变量中
                var bname=lineArr[i].name;
                var sstop=lineArr[i].start_stop;
                var estop=lineArr[i].end_stop;
                var pathArr = lineArr[i].path;//公交线路经纬度
                var stops = lineArr[i].via_stops;//途径站
                var startPot = stops[0].location;//首发站经纬度
                var endPot = stops[stops.length - 1].location;//终点站经纬度
                var stime=lineArr[i].stime;
                var etime=lineArr[i].etime;
                var bprice=lineArr[i].basic_price;
                var tprice=lineArr[i].total_price;
                var dis=lineArr[i].distance;
                console.log('已找到',bname);
                textlog('*已找到'+bname);
                savedata(bname,sstop,estop,stops,stime,etime,bprice,tprice,pathArr,dis);
                if (i == 0) //只绘制一条线路
                    drawbusLine(startPot, endPot, pathArr);//绘制线路的函数，如下面指定
            }
        }
    }

    //绘制路线
    function drawbusLine(startPot, endPot, BusArr) {
        //绘制起点，终点
        new AMap.Marker({
            map: map,
            position: startPot, //基点位置
            icon: "https://webapi.amap.com/theme/v1.3/markers/n/start.png",
            zIndex: 10
        });
        new AMap.Marker({
            map: map,
            position: endPot, //基点位置
            icon: "https://webapi.amap.com/theme/v1.3/markers/n/end.png",
            zIndex: 10
        });
        //绘制乘车的路线
        busPolyline = new AMap.Polyline({
            map: map,
            path: BusArr,
            strokeColor: "#0099ff",//线颜色
            strokeOpacity: 0.8,//线透明度
            isOutline: true,
            outlineColor: 'white',
            strokeWeight: 6//线宽
        });
        map.setFitView();
    }

    function savedata(bname,sstop,estop,stops,stime,etime,bprice,tprice,pathArr,dis){
        //线路信息总表
        buslines.push({linename:bname,start:sstop,end:estop,stime:stime,etime:etime,bprice:bprice,tprice:tprice,dis:dis});
        //线路沿线站点总表
        for(var i=0;i<stops.length;++i){
            busstops.push({linename:bname,id:stops[i]['id'],stopname:stops[i]['name'],sequence:stops[i]['sequence'],
                    lng:stops[i]['location']['lng'],lat:stops[i]['location']['lat']});
        }
        //线路绘制点位总表——不知道为什么没有办法在这一步完成，又是“异步”的问题？
        for (var j=0;j<pathArr[0].length;++j){
            console.log(bname,j+1);
            linedraw.push({linename:bname,order:j+1,lng:pathArr[0][j]['lng'],lat:pathArr[0][j]['lat']});
        }
        linedraw.push({line:bname,path:pathArr});
    }


    //定义休眠函数，模拟人工获取数据，避免后期高德封号
    function sleep (time) {
      return new Promise((resolve) => setTimeout(resolve, time));
    }
    async function startget() {
        //利用for循环遍历所有目标公交线路
        for (var j = 1; j < totalnum+1; j++) {
          console.log('正在搜索'+names[j]+'…………');
          textlog('正在搜索'+names[j]+'…………');
          await sleep(2000);
          lineSearch(names[j]);
        }
    }

    //查询数据获取进度
    function how(){
        //线路绘制点位总表
        /*for(var i=0;i<linedraw.length;++i){
            for (var j=0;j<linedraw[i]['path'].length;++j){
                drawlines.push({linename:linedraw[i]['line'],order:j+1,lng:linedraw[i]['path'][j]['lng'],
                    lat:linedraw[i]['path'][j]['lat']});
            }
        }*/
        var a=buslines.length;
        var b=buslost.length;
        var t=totalnum;
        textlog('…………')
        if(t==a){
            console.log('目标线路：',t,'当前线路：',a);
            textlog('目标线路：'+t+'当前线路：'+a);
            console.log('*#*单向线路数据已全部获取完成');
            textlog('*#*单向线路数据已全部获取完成');
        }else{
            console.log('目标线路：',t,'当前线路：',a,'未查询到',b);
            textlog('目标线路：'+t+'当前线路：'+a+'未查询到'+b);
        }
        if (CheckNum < totalnum){
            var c=totalnum-CheckNum
            textlog('双向线路已比对：'+c+'，剩余：'+CheckNum)
        }
        textlog('…………')
    }
    document.getElementById('start').onclick = startget;


    /* 从返回的信息定位到公交线路result对象：
    plans数组-0对象方案1-segments数组路线分段-1对象分段2-transit_mode:"BUS"-lines数组线路信息，path数组公交线路,
    via_stop数组沿途站点
    result即是对应的公交路线数据信息，相关数据结构文档请参考
    https://lbs.amap.com/api/javascript-api/reference/route-search#m_TransferResult
    公交线路数据names，查询到的始末站buslines[0]['start','end']*/

    //往返公交线路的比对-公交线路规划
    //按钮触发
    document.getElementById('LineCheck').onclick = StartCheck;

    //开始公交线路规划
    async function StartCheck() {
        textlog("###开始比对往返线路")
        CheckNum = totalnum
        //利用for循环遍历所有目标公交线路
        for (var j = 1; j < totalnum+1; j++) {
          console.log('*正在比对'+names[j]+'…………');
          textlog('*正在比对'+names[j]+'…………');
          CheckNum--
          await sleep(2000);
          lineTransfer(buslines[j-1]['linename'], buslines[j-1]['end'], buslines[j-1]['start']);
          lineTransfer(buslines[j-1]['linename'], buslines[j-1]['start'], buslines[j-1]['end']);
        }
    }

    function lineTransfer(line, start_stop, end_stop){
        //换乘设置
        var transOptions = {
                map: map,
                city: (names[0]+'2').substr(0,3),
                panel: 'panel',
                policy: AMap.TransferPolicy.LEAST_TRANSFER //乘车策略：最少换乘，利于获取单一线路数据
            };
        //构造公交换乘类
        var transfer = new AMap.Transfer(transOptions);
        /*根据起、终点名称查询公交换乘路线
        比对信息：两条公交线路距离不同、站点不同
        plans[0]-segments[1](transit_mod:'BUS')['distance']

        先确认线路能够找到，然后进行路线规划，保存双向所有信息（该条公交线路部分）
        */

        //一个方向
        transfer.search([
            // 排除了一种情况：公交线路为环路，起终点一致，不会返回线路规划数据，但是公交线路查询，可以获得数据
            {keyword: start_stop},
            {keyword: end_stop}
        ], function(status, result) {
            //console.log(result)
            if (status === 'complete') {
                log.success('绘制公交路线完成')
                textlog('*'+start_stop+'--'+end_stop)
                //选择数据
                var plans = result['plans']
                var num_plans = plans.length
                for (var i = 0; i < num_plans; i++) {
                    var num_segments = plans[i]['segments'].length
                    LinesPick(line, plans, i, num_segments)}
            } else {
                log.error('公交路线数据查询失败' + result)
            }
        });
    }

    //挑选出一个方向的公交线路数据,data=result['plans']
    function LinesPick(line, data, i, num_segments){
        for (var j = 0; j < num_segments; j++) {
            let line0 = data[i]['segments'][j]
            if (line0['transit_mode'] == 'BUS') {
                //取线路名称做比对
                var line0name = line0['transit']['lines'][0]['name'].split('(')[0]
                var line = line.split('(')[0]
                if (line0name == line) {
                    var bus_data= line0['transit']
                    var name = bus_data['lines'][0]['name']
                    var on = bus_data['on_station']
                    var off = bus_data['off_station']
                    var path = bus_data['path']
                    var pathdraw = []
                    for (k=0; k<path.length; ++k){
                        pathdraw.push({order:k+1,lat:path[k]['lat'],lng:path[k]['lng']})
                    }
                    var stops = bus_data['via_stops']
                    // 上车站
                    var stops2 = [{sequence:1,stopname:on['name'],lat:on['location']['lat'],lng:on['location']['lng']}]
                    // 中间站
                    for (k=0; k<stops.length; ++k){
                        stops2.push({sequence:k+2,stopname:stops[k]['name'],lat:stops[k]['location']['lat'],lng:stops[k]['location']['lng']})
                    }
                    // 下车站
                    stops2.push({sequence:stops2.length+1,stopname:off['name'],lat:off['location']['lat'],lng:off['location']['lng']})
                    BusInfo.push({linename:name,start:on['name'],end:off['name'],pathdraw:pathdraw,stops:stops2})
                }
            }
        }
    }
</script>
<!--将查询结果导出为json文件-->
<script>
    var button = document.getElementById("lines");
    button.addEventListener("click",saveHandler, false);//saveHandler无法传递表格、表格名称，否则打开页面会首先执行并下载生成空文件
    function saveHandler(){
        var content = JSON.stringify(buslines);
        var blob = new Blob([content],{type: "text/plain;charset=utf-8"});
        saveAs(blob, "BusLines.json");
    }
</script>
<script>
    var button = document.getElementById("stops");
    button.addEventListener("click",saveHandler, false);
    function saveHandler(){
        var content = JSON.stringify(busstops);
        var blob = new Blob([content], {type: "text/plain;charset=utf-8"});
        saveAs(blob, "BusStops.json");
    }
</script>
<script>
    var button = document.getElementById("dots");
    button.addEventListener("click",saveHandler, false);
    function saveHandler(){
        for(var i=0;i<linedraw.length;++i){
            for (var j=0;j<linedraw[i]['path'].length;++j){
                drawlines.push({linename:linedraw[i]['line'],order:j+1,lng:linedraw[i]['path'][j]['lng'],
                    lat:linedraw[i]['path'][j]['lat']});
            }
        }
        var content = JSON.stringify(drawlines);
        var blob = new Blob([content], {type: "text/plain;charset=utf-8"});
        saveAs(blob, "DrawLines.json");
    }
</script>
<script>
    var button = document.getElementById("lost");
    button.addEventListener("click",saveHandler, false);
    function saveHandler(){
        var content = buslost;
        var blob = new Blob([content], {type: "text/plain;charset=utf-8"});
        saveAs(blob, "LostLines.txt");
    }
</script>
<script>
    var button = document.getElementById("compare");
    button.addEventListener("click",saveHandler, false);
    function saveHandler(){
        /*
        不知道为什么BusInfo会存在循环引用的情况，储存时总报错
        于是就找到了这些代码去进一步处理，参考：https://cloud.tencent.com/developer/article/1653935
        */
        // 声明cache变量，便于匹配是否有循环引用的情况
        var cache = [];
        var str = JSON.stringify(BusInfo, function(key, value) {
            if (typeof value === 'object' && value !== null) {
                if (cache.indexOf(value) !== -1) {
                    // 移除
                    return;
                }
                // 收集所有的值
                cache.push(value);
            }
            return value;
        });
        cache = null; // 清空变量，便于垃圾回收机制回收
        var blob = new Blob([str], {type: "text/plain;charset=utf-8"});
        saveAs(blob, "LinesDataCompare.json");
    }
</script>
</body>
</html>